package com.fary.boot.autoconfigure;

import com.fary.boot.context.event.ApplicationEnvironmentPreparedEvent;
import com.fary.boot.context.event.ApplicationFailedEvent;
import com.fary.boot.context.event.ApplicationReadyEvent;
import com.fary.boot.context.event.SpringApplicationEvent;
import com.fary.boot.context.logging.LoggingApplicationListener;
import com.fary.context.ApplicationListener;
import com.fary.core.annotation.Order;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 尽早触发一些耗时的初始化任务，使用一个后台线程
 */
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

    /**
     * System property that instructs Spring Boot how to run pre initialization. When the
     * property is set to {@code true}, no pre-initialization happens and each item is
     * initialized in the foreground as it needs to. When the property is {@code false}
     * (default), pre initialization runs in a separate thread in the background.
     *
     * @since 2.1.0
     */
    public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";

    // 记录预初始化任务是否已经在执行，初始值设置为false，表示未开始
    private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);

    // 记录预初始化任务是否已经完成，1表示未完成，0表示完成
    private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);

    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        // spring.backgroundpreinitializer.ignore默认为false，开启后台线程初始化
        // 如果当前事件是ApplicationStartingEvent，并且预初始化任务尚未执行
        // 则 :将preinitializationStarted设置为预初始化任务开始执行;
        // 开始执行预初始化任务;

        if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
                && event instanceof ApplicationEnvironmentPreparedEvent
                && multipleProcessors()
                && preinitializationStarted.compareAndSet(false, true)) {
            performPreinitialization();
        }
        if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
                && preinitializationStarted.get()) {
            try {
                preinitializationComplete.await();
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private boolean multipleProcessors() {
        return Runtime.getRuntime().availableProcessors() > 1;
    }

    private void performPreinitialization() {
        try {
            Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    runSafely(new ConversionServiceInitializer());
                    runSafely(new ValidationInitializer());
                    runSafely(new MessageConverterInitializer());
                    runSafely(new JacksonInitializer());
                    runSafely(new CharsetInitializer());
                    preinitializationComplete.countDown();
                }

                public void runSafely(Runnable runnable) {
                    try {
                        runnable.run();
                    } catch (Throwable ex) {
                        // Ignore
                    }
                }

            }, "background-preinit");
            thread.start();
        } catch (Exception ex) {
            // This will fail on GAE where creating threads is prohibited. We can safely
            // continue but startup will be slightly slower as the initialization will now
            // happen on the main thread.
            preinitializationComplete.countDown();
        }
    }

    /**
     * Early initializer for Spring MessageConverters.
     */
    private static class MessageConverterInitializer implements Runnable {

        @Override
        public void run() {
            new AllEncompassingFormHttpMessageConverter();
        }

    }

    /**
     * Early initializer for javax.validation.
     */
    private static class ValidationInitializer implements Runnable {

        @Override
        public void run() {
            Configuration<?> configuration = Validation.byDefaultProvider().configure();
            configuration.buildValidatorFactory().getValidator();
        }

    }

    /**
     * Early initializer for Jackson.
     */
    private static class JacksonInitializer implements Runnable {

        @Override
        public void run() {
            Jackson2ObjectMapperBuilder.json().build();
        }

    }

    /**
     * Early initializer for Spring's ConversionService.
     */
    private static class ConversionServiceInitializer implements Runnable {

        @Override
        public void run() {
            new DefaultFormattingConversionService();
        }

    }

    private static class CharsetInitializer implements Runnable {

        @Override
        public void run() {
            StandardCharsets.UTF_8.name();
        }

    }

}